<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>调度执行</title>
</head>
<body>
  <div id="app"></div>
  <div id="app1"></div>
<script>
function reactive(data) {
  return new Proxy(data, {
    
    get(target, key, receive) {
      const res = Reflect.get(target, key, receive)
      track(target, key)
      return res
    },
    set(target, key, newValue, receive) {
      const res = Reflect.set(target, key, newValue, receive)
      trigger(target, key)
      return res
    }
  })
}
//依赖存储器
const bucket = new WeakMap() //obj->k->set

function track(target, key) {
  if (!activeEffect) return
  let depsMap = bucket.get(target)
  if (!depsMap) {
    depsMap = new Map()
    bucket.set(target, depsMap)
  }
  let deps = depsMap.get(key)
  if (!deps) {
    deps = new Set()
    depsMap.set(key, deps)
  }
  deps.add(activeEffect)
  activeEffect.deps.push(deps)
}

function trigger(target, key) {
  const depsMap = bucket.get(target)
  if (!depsMap) return
  const effects = depsMap.get(key)
  const deps = new Set(effects)
  deps && deps.forEach((fn) => {
    if(fn !== activeEffect) {
      const {scheduler} = fn.options
      if(scheduler) {
        scheduler(fn)
      } else {
        fn()
      }
    }
  })
}

let activeEffect = null
const effectStack = []
function effect(fn, options = {}) {
  const effectFn = ()=>{
    cleanup(effectFn)
    //当调用 effect 注册副作用函数时，将副作用函数赋值给activeEffect
    activeEffect = effectFn
    //在调用副作用函数之前将当前副作用函数压入栈中
    effectStack.push(effectFn)
    fn()
    //在当前副作用函数执行完毕后，将当前副作用函数弹出栈，并把activeEffect还原之前的值
    effectStack.pop()
    activeEffect = effectStack.length - 1 > 0 && activeEffect[effectStack.length - 1]
  }
  effectFn.deps = []
  effectFn.options = options
  effectFn()
}

function cleanup(effectFn) {
  const {deps} = effectFn
  for(let i = 0; i < deps.length; i++ ) {
    deps[i].delete(effectFn)
  }
  effectFn.deps.length = 0
}
    
</script>

 <script>
  const data = {foo :1}
  const obj = reactive(data)
  effect(()=> {
    console.log(obj.foo)
  })
  obj.foo ++
  console.log('结束了obj')

  //更改执行时机
  const data1 = {foo: 11}
  const obj1 = reactive(data1)
  effect(()=> {
    console.log(obj1.foo)
  }, {
    scheduler(fn) {
      setTimeout(fn)
    }
  })
  obj1.foo ++
  console.log('结束了obj1')

  const aa = {foo:1}
  const objA = reactive(aa)
  effect(()=> {
    console.log(objA.foo)
  })
  objA.foo++
  objA.foo++

  //更改执行次数
  const jobQueue = new Set()
  const p = Promise.resolve()
  let isFlushing = false
  function flushJob() {
    if(isFlushing) return
    isFlushing = true
    p.then(()=>{
      jobQueue.forEach(job => {
        job()
      })
    }).finally(()=> {
      isFlushing = false
    })
  }
  const bb = {foo:111}
  const objB = reactive(bb)
  effect(()=> {
    console.log(objB.foo)
  }, {
    scheduler(fn) {
      jobQueue.add(fn)
      flushJob()
    }
  })
  objB.foo++
  objB.foo++
 </script>
</body>
</html>